/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package pl.edu.uw.come.mooba;

import pl.edu.uw.come.mooba.util.ZipArchiveTools;
import pl.edu.uw.come.mooba.domain.exceptions.BackupDescFileParsingException;
import pl.edu.uw.come.mooba.domain.exceptions.InvalidMoodleBackupArchiveException;
import pl.edu.uw.come.mooba.util.Config;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipFile;
import javax.xml.xpath.XPathExpressionException;
import pl.edu.uw.come.mooba.domain.BackupInfo;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

import org.xml.sax.SAXException;
import pl.edu.uw.come.mooba.domain.CourseInfo;
import pl.edu.uw.come.mooba.domain.Log;
import pl.edu.uw.come.mooba.domain.User;
import pl.edu.uw.come.mooba.util.MyErrorHandler;

import static pl.edu.uw.come.mooba.util.DateUtils.strToCalendar;

/**
 *
 * @author Piotr Kochański, Tomasz Rybicki
 */
public class DomBackupParser {
    private File workingFolder = new File(System.getProperty("user.dir"));
    private Document backupDescFile;
    private XPath xpath;

    public DomBackupParser(ZipFile backupFile) throws IOException, InvalidMoodleBackupArchiveException, BackupDescFileParsingException {
        ZipArchiveTools.extractMoodle1XBackupFile(backupFile, workingFolder);
        initMoodle1XBackupDocument();
        XPathFactory xpathFactory = XPathFactory.newInstance();
        xpath = xpathFactory.newXPath();
    }

    /**
     * Creates DOM Document object
     */
    private void initMoodle1XBackupDocument() throws BackupDescFileParsingException {
        try {
            DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
            // Namespace awareness
            domFactory.setNamespaceAware(false);
            // Moodle backup file is not properly formed
            domFactory.setValidating(false);
            DocumentBuilder parser = domFactory.newDocumentBuilder();
            parser.setErrorHandler(new MyErrorHandler());
            //analizuje plik i na jego podstawie tworzy
            //obiekt Document
            backupDescFile = parser.parse(workingFolder + File.separator + Config.BACKUP_DESC_FILENAME);
        } catch (SAXException ex) {
            Logger.getLogger(DomBackupParser.class.getName()).log(Level.SEVERE, null, ex);
            throw new BackupDescFileParsingException("Unable to parse backup file", ex);
        } catch (IOException ex) {
            Logger.getLogger(DomBackupParser.class.getName()).log(Level.SEVERE, null, ex);
            throw new BackupDescFileParsingException("Unable to parse backup file", ex);
        } catch (ParserConfigurationException ex) {
            Logger.getLogger(DomBackupParser.class.getName()).log(Level.SEVERE, null, ex);
            throw new BackupDescFileParsingException("Unable to parse backup file", ex);
        }
    }

    public BackupInfo getBackupInfo() throws BackupDescFileParsingException {
        String basePath = BackupInfo.BASE_XPATH;
        BackupInfo info = null;
        try {
            info = new BackupInfo();
            XPathExpression namePath = xpath.compile(basePath + BackupInfo.ARCHIVE_FILE_NAME_TAG + "/text()");
            String name = (String) namePath.evaluate(backupDescFile, XPathConstants.STRING);
            info.setArchFileName(name);

            XPathExpression backupDatePath = xpath.compile(basePath + BackupInfo.BACKUP_DATE_TAG + "/text()");
            String backupDate = (String) backupDatePath.evaluate(backupDescFile, XPathConstants.STRING);
            info.setBackupDate(strToCalendar(backupDate));

            XPathExpression backupReleasePath = xpath.compile(basePath + BackupInfo.BACKUP_RELEASE_TAG + "/text()");
            String backupRelease = (String) backupReleasePath.evaluate(backupDescFile, XPathConstants.STRING);
            info.setBackupRelease(backupRelease);

            XPathExpression backupVerPath = xpath.compile(basePath + BackupInfo.BACKUP_VERSION_TAG + "/text()");
            String backupVer = (String) backupVerPath.evaluate(backupDescFile, XPathConstants.STRING);
            info.setBackupVersion(backupVer);

            XPathExpression moodleRelasePath = xpath.compile(basePath + BackupInfo.MOODLE_RELEASE_TAG + "/text()");
            String moodleRelase = (String) moodleRelasePath.evaluate(backupDescFile, XPathConstants.STRING);
            info.setMoodleRelease(moodleRelase);

            XPathExpression moodleVerPath = xpath.compile(basePath + BackupInfo.MOODLE_VERSION_TAG + "/text()");
            String moodleVer = (String) moodleVerPath.evaluate(backupDescFile, XPathConstants.STRING);
            info.setMoodleVersion(moodleVer);

            XPathExpression wwwRootPath = xpath.compile(basePath + BackupInfo.ORIGINAL_WWWROOT_TAG + "/text()");
            String wwwRoot = (String) wwwRootPath.evaluate(backupDescFile, XPathConstants.STRING);
            info.setOriginalWWWroot(new URL(wwwRoot));

        } catch (MalformedURLException ex) {
            Logger.getLogger(DomBackupParser.class.getName()).log(Level.SEVERE, null, ex);
            throw new BackupDescFileParsingException("Wrong WWW ROOT URL", ex);
        } catch (XPathExpressionException ex) {
            Logger.getLogger(DomBackupParser.class.getName()).log(Level.SEVERE, null, ex);
            throw new BackupDescFileParsingException("Wrong XPATH expression. Internal error.", ex);
        }
        return info;
    }

    public CourseInfo getCourseInfo() throws BackupDescFileParsingException {
        String basePath = CourseInfo.BASE_XPATH;
        CourseInfo info = null;
        try {
            info = new CourseInfo();
            XPathExpression idPath = xpath.compile(basePath + CourseInfo.ID_TAG + "/text()");
            String id = (String) idPath.evaluate(backupDescFile, XPathConstants.STRING);
            info.setId(Long.valueOf(id));

            XPathExpression shortnamePath = xpath.compile(basePath + CourseInfo.SHORTNAME_TAG + "/text()");
            String shortname = (String) shortnamePath.evaluate(backupDescFile, XPathConstants.STRING);
            info.setShortname(shortname);

            XPathExpression fullnamePath = xpath.compile(basePath + CourseInfo.FULLNAME_TAG + "/text()");
            String fullname = (String) fullnamePath.evaluate(backupDescFile, XPathConstants.STRING);
            info.setFullname(fullname);

            XPathExpression categoryNamePath = xpath.compile(basePath + CourseInfo.CATEGORY_NAME_TAG + "/text()");
            String categoryName = (String) categoryNamePath.evaluate(backupDescFile, XPathConstants.STRING);
            info.setCategoryName(categoryName);

        } catch (XPathExpressionException ex) {
            Logger.getLogger(DomBackupParser.class.getName()).log(Level.SEVERE, null, ex);
            throw new BackupDescFileParsingException("Wrong XPATH expression. Internal error.", ex);
        }
        return info;
    }

    /**
     * Returns number of users with the give role or number of all users if role is null.
     * @param roleShortname roleShortname moodle role short name (right now only student is supported)
     * @return number of all users if <code>roleShortname</code> is null or number of users with the given role
     * @throws BackupDescFileParsingException
     */
    public int getNumberOfUsers(String roleShortname) throws BackupDescFileParsingException {
        try {
            XPathExpression namePath;
            NodeList usersRoot;

            if (roleShortname == null) {
                namePath = xpath.compile(User.XPATH);
                usersRoot = (NodeList) namePath.evaluate(backupDescFile, XPathConstants.NODESET);
                return usersRoot.getLength();
            } else {
                namePath = xpath.compile(User.getXpath(roleShortname));
                usersRoot = (NodeList) namePath.evaluate(backupDescFile, XPathConstants.NODESET);
                return usersRoot.getLength();
            }

        } catch (XPathExpressionException ex) {
            Logger.getLogger(DomBackupParser.class.getName()).log(Level.SEVERE, null, ex);
            throw new BackupDescFileParsingException("Wrong XPATH expression. Internal error.", ex);
        }
    }
    /**
     * Returns list of users with the give role or all users id role is null
     *
     * @param roleShortname moodle role short name (right now only student is supported)
     * @return all users if <code>roleShortname</code> is null or users with the given role
     * @throws BackupDescFileParsingException
     */
    public List<User> getUsers(String roleShortname) throws BackupDescFileParsingException {

        List<User> uList = null;
        try {
            uList = new ArrayList<User>();
            // a set for 'kosher' ids
            HashSet<String> userIDs = new HashSet<String>();

            if (roleShortname != null) {
                // choosing ID's of users that has a student role
                XPathExpression rolePath = xpath.compile(User.getXpath(roleShortname));
                NodeList userIDList = (NodeList) rolePath.evaluate(backupDescFile, XPathConstants.NODESET);

                // iterating through nodes in DOM to find role assignments
                for (int j = 0; j < userIDList.getLength(); j++) {
                    NodeList ids = (NodeList) userIDList.item(j);
                    for (int i = 0; i < ids.getLength(); i++) {
                        if (ids.item(i).getFirstChild() != null) {
                            String nodeName = ids.item(i).getFirstChild().getParentNode().getNodeName();
                            String nodeVal = ids.item(i).getFirstChild().getTextContent();
                            if (nodeName.equalsIgnoreCase("USERID")) {
                                userIDs.add(nodeVal);
                            }
                        }
                    }
                }
            }

            XPathExpression namePath = xpath.compile(User.XPATH);
            NodeList usersRoot = (NodeList) namePath.evaluate(backupDescFile, XPathConstants.NODESET);

            // user is kosher if their id is in userIDs set
            boolean kosher = true;

            for (int j = 0; j < usersRoot.getLength(); j++) {
                NodeList users = (NodeList) usersRoot.item(j);
                User u = new User();
                kosher = true;
                for (int i = 0; i < users.getLength(); i++) {
                    if (users.item(i).getFirstChild() != null) {
                        String nodeName = users.item(i).getFirstChild().getParentNode().getNodeName();
                        String nodeVal = users.item(i).getFirstChild().getTextContent();

                        //if user id is not 'kosher'..
                        if (roleShortname != null && nodeName.equalsIgnoreCase(User.ID_TAG)) {
                            if (!userIDs.contains(nodeVal)) {
                                kosher = false;
                                break; //..skip him
                            }
                        }

                        if (nodeName.equalsIgnoreCase(User.AUTH_TAG)) {
                            u.setAuth(nodeVal);
                        }
                        if (nodeName.equalsIgnoreCase(User.CONFIRMED_TAG)) {
                            u.setConfirmed(nodeVal.equals("1") ? true : false);
                        }
                        if (nodeName.equalsIgnoreCase(User.DELETED_TAG)) {
                            u.setDeleted(nodeVal.equals("1") ? true : false);
                        }
                        if (nodeName.equalsIgnoreCase(User.DEPARTMENT_TAG)) {
                            u.setDepartment(nodeVal);
                        }
                        if (nodeName.equalsIgnoreCase(User.EMAIL_TAG)) {
                            u.setEmail(nodeVal);
                        }
                        if (nodeName.equalsIgnoreCase(User.FIRSTACCESS_TAG)) {
                            u.setFirstaccess(strToCalendar(nodeVal));
                        }
                        if (nodeName.equalsIgnoreCase(User.ID_TAG)) {
                            u.setId(Long.valueOf(nodeVal));
                        }
                        if (nodeName.equalsIgnoreCase(User.IDNUMBER_TAG)) {
                            u.setIdnumber(nodeVal);
                        }
                        if (nodeName.equalsIgnoreCase(User.INSTITUTION_TAG)) {
                            u.setInstitution(nodeVal);
                        }
                        if (nodeName.equalsIgnoreCase(User.LASTACCESS_TAG)) {
                            u.setLastaccess(strToCalendar(nodeVal));
                        }
                        if (nodeName.equalsIgnoreCase(User.LASTLOGIN_TAG)) {
                            u.setLastlogin(strToCalendar(nodeVal));
                        }
                        if (nodeName.equalsIgnoreCase(User.LASTNAME_TAG)) {
                            u.setLastname(nodeVal);
                        }
                        if (nodeName.equalsIgnoreCase(User.USERNAME_TAG)) {
                            u.setUsername(nodeVal);
                        }
                        if (nodeName.equalsIgnoreCase(User.FIRSTNAME_TAG)) {
                            u.setFirstname(nodeVal);
                        }
                    }
                }
                // we want only kosher users in our boodle
                if (kosher) {
                    uList.add(u);
                }
            }



        } catch (XPathExpressionException ex) {
            Logger.getLogger(DomBackupParser.class.getName()).log(Level.SEVERE, null, ex);
            throw new BackupDescFileParsingException("Wrong XPATH expression. Internal error.", ex);
        }
        return uList;

    }

    public List<Log> getLogs() throws BackupDescFileParsingException {

        List<Log> logList = null;
        try {
            logList = new ArrayList<Log>();
            XPathExpression namePath = xpath.compile(Log.XPATH);
            NodeList logsRoot = (NodeList) namePath.evaluate(backupDescFile, XPathConstants.NODESET);

            for (int j = 0; j < logsRoot.getLength(); j++) {
                NodeList users = (NodeList) logsRoot.item(j);
                Log log = new Log();
                for (int i = 0; i < users.getLength(); i++) {
                    if (users.item(i).getFirstChild() != null) {
                        String nodeName = users.item(i).getFirstChild().getParentNode().getNodeName();
                        String nodeVal = users.item(i).getFirstChild().getTextContent();
                        if (nodeName.equalsIgnoreCase(Log.ACTION_TAG)) {
                            log.setAction(nodeVal);
                        }
                        if (nodeName.equalsIgnoreCase(Log.CMID_TAG)) {
                            log.setCmid(Long.valueOf(nodeVal));
                        }
                        if (nodeName.equalsIgnoreCase(Log.ID_TAG)) {
                            log.setId(Long.valueOf(nodeVal));
                        }
                        if (nodeName.equalsIgnoreCase(Log.INFO_TAG)) {
                            log.setInfo(nodeVal);
                        }
                        if (nodeName.equalsIgnoreCase(Log.IP_TAG)) {
                            log.setIp(nodeVal);
                        }
                        if (nodeName.equalsIgnoreCase(Log.MODULE_TAG)) {
                            log.setModule(nodeVal);
                        }
                        if (nodeName.equalsIgnoreCase(Log.TIME_TAG)) {
                            log.setTime(strToCalendar(nodeVal));
                        }
                        if (nodeName.equalsIgnoreCase(Log.URL_TAG)) {
                            log.setUrl(nodeVal);
                        }
                        if (nodeName.equalsIgnoreCase(Log.USERID_TAG)) {
                            log.setUserid(Long.valueOf(nodeVal));
                        }
                    }
                }
                logList.add(log);
            }

        } catch (XPathExpressionException ex) {
            Logger.getLogger(DomBackupParser.class.getName()).log(Level.SEVERE, null, ex);
            throw new BackupDescFileParsingException("Wrong XPATH expression. Internal error.", ex);
        }
        return logList.size() > 0 ? logList : null;

    }
}
